﻿using Grpc.Core;
using Microsoft.Extensions.Logging;
using Polly;
using QPZS.ETKA.VinQueryService;
using System;
using System.Runtime.CompilerServices;
using Wicture.DbRESTFul;
using static QPZS.ETKA.VinQueryService.VinQueryRpc;

namespace Wicture.QPZS.EPC
{
    public static class VinQueryRpcServiceClient
    {
        private static object locker = new object();
        private static Channel channel;
        private static string channelAddress;
        private const int RetryTimes = 2; // 失败后，最多重试次数

        static VinQueryRpcServiceClient()
        {
            channelAddress = ConfigurationManager.Settings.Variables["ETKA.VinQueryService"];
        }

        private static Channel GetOrOpen()
        {
            if (string.IsNullOrEmpty(channelAddress)) throw new Exception($"请先通过 Init 初始化。");

            if (channel == null || channel.State == ChannelState.Shutdown || channel.State == ChannelState.TransientFailure)
            {
                LoggerManager.Logger.LogInformation($"Need to create channel due to: {channel?.State}.");

                try
                {
                    lock (locker)
                    {
                        if (channel == null || channel.State == ChannelState.Shutdown || channel.State == ChannelState.TransientFailure)
                        {
                            channel?.ShutdownAsync().Wait();
                            channel = null;
                        }

                        if (channel == null)
                        {
                            LoggerManager.Logger.LogInformation($"初始化RPC通道: {channelAddress}。");
                            channel = new Channel(channelAddress, ChannelCredentials.Insecure);
                        }
                    }
                }
                catch (Exception ex)
                {
                    channel?.ShutdownAsync().Wait();
                    channel = null;
                    LoggerManager.Logger.LogError($"通道创建失败: {channelAddress}。", ex);
                    throw ex;
                }
            }

            LoggerManager.Logger.LogInformation($"Channel state: {channel.State}.");
            return channel;
        }

        private static void Dispose()
        {
            channel?.ShutdownAsync().Wait();
            channel = null;
        }

        public static ETKAVin QueryETKA(string vin)
        {
            var result = Call(client => client.QueryETKA(new VinRequest { Code = vin, IsEtka = true }));
            if (result.HasError)
            {
                throw new LogicalException(result.Message);
            }
            else if(result.Data == null)
            {
                throw new LogicalException($"输入的VIN码无法解析。", 610);
            }

            return result.Data;
        }

        public static PorscheVin QueryPorsche(string vin)
        {
            var result = Call(client => client.QueryPorsche(new VinRequest { Code = vin, IsEtka = false }));
            if (result.HasError)
            {
                throw new LogicalException(result.Message);
            }
            else if (result.Data == null)
            {
                throw new LogicalException($"输入的VIN码无法解析。", 610);
            }

            return result.Data;
        }

        public static AgentResponse Agents(EmptyRequest request)
        {
            return Call(client => client.Agents(request));
        }

        private static T Call<T>(Func<VinQueryRpcClient, T> func, [CallerMemberName]string methodName = "")
        {
            try
            {
                var client = new VinQueryRpcClient(GetOrOpen());
                LoggerManager.Logger.LogInformation($"Invoking {methodName}");
                var result = func.Invoke(client);
                LoggerManager.Logger.LogInformation($"{methodName}调用成功。");
                return result;
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError($"调用 VinQueryRpcClient.{methodName} 出错。", ex);
                return default(T);
            }
            finally
            {
                Dispose();
            }
        }
    }
}
